#pragma once

#include <cstdint>

// Processor architecture detection.  For more info on what's defined, see:
//   http://msdn.microsoft.com/en-us/library/b0084kay.aspx
//   http://www.agner.org/optimize/calling_conventions.pdf
//   or with gcc, run: "echo | gcc -E -dM -"
#if defined(_M_X64) || defined(__x86_64__)
#define WEBRTC_ARCH_X86_FAMILY
#define WEBRTC_ARCH_X86_64
#define WEBRTC_ARCH_64_BITS
#define WEBRTC_ARCH_LITTLE_ENDIAN
#elif defined(__aarch64__)
#define WEBRTC_ARCH_ARM_FAMILY
#define WEBRTC_ARCH_64_BITS
#define WEBRTC_ARCH_LITTLE_ENDIAN
#elif defined(_M_IX86) || defined(__i386__)
#define WEBRTC_ARCH_X86_FAMILY
#define WEBRTC_ARCH_X86
#define WEBRTC_ARCH_32_BITS
#define WEBRTC_ARCH_LITTLE_ENDIAN
#elif defined(__ARMEL__)
#define WEBRTC_ARCH_ARM_FAMILY
#define WEBRTC_ARCH_32_BITS
#define WEBRTC_ARCH_LITTLE_ENDIAN
#elif defined(__MIPSEL__)
#define WEBRTC_ARCH_MIPS_FAMILY
#if defined(__LP64__)
#define WEBRTC_ARCH_64_BITS
#else
#define WEBRTC_ARCH_32_BITS
#endif
#define WEBRTC_ARCH_LITTLE_ENDIAN
#elif defined(__pnacl__)
#define WEBRTC_ARCH_32_BITS
#define WEBRTC_ARCH_LITTLE_ENDIAN
#else
#error Please add support for your architecture in typedefs.h
#endif

#if !(defined(WEBRTC_ARCH_LITTLE_ENDIAN) ^ defined(WEBRTC_ARCH_BIG_ENDIAN))
#error Define either WEBRTC_ARCH_LITTLE_ENDIAN or WEBRTC_ARCH_BIG_ENDIAN
#endif

// List of features in x86.
enum class X86CpuFeature
{
    kSSE2,
    kSSE3,
    kSSSE3,
    kSSE4_1,
    kSSE4_2,
    kAVX,
    kAVX2,
    kNEON,
    kNone
};

// List of features in ARM.
enum class ArmCpuFeature
{
    kCPUFeatureARMv7      = (1 << 0),
    kCPUFeatureVFPv3      = (1 << 1),
    kCPUFeatureNEON       = (1 << 2),
    kCPUFeatureLDREXSTREX = (1 << 3)
};

#include <array>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <memory>
#include <mutex>
#include <vector>

constexpr auto GF_BITS      = 8;
constexpr auto GF_SIZE      = (1 << GF_BITS) - 1;
constexpr auto GF_ENTRY_LEN = 32;

using gf = unsigned char;

struct fec_parms
{
    int             k, n; /* parameters of the code */
    std::vector<gf> enc_matrix;
};

using CRSparmsMap = std::map<unsigned int, std::unique_ptr<fec_parms>>;

class RsFecTable : public std::enable_shared_from_this<RsFecTable>
{
public:
    static std::shared_ptr<RsFecTable> get_this();
    gf                                 modnn(int x);

    gf**                        gf_mul_table;
    gf**                        gf_mul_zip_table;
    std::array<gf, 2 * GF_SIZE> gf_exp;
    std::array<gf, GF_SIZE + 1> gf_log;
    std::array<gf, GF_SIZE + 1> inverse;
    X86CpuFeature               m_cpu_flag;

    virtual ~RsFecTable();

    RsFecTable();

    void init_rscoding();
    void uninit_rscoding();

    RsFecTable(const RsFecTable&)            = delete;
    RsFecTable& operator=(const RsFecTable&) = delete;

    static std::mutex& mutex();

private:
    void generate_gf();
    void init_mul_table();
    void init_cpu_flag();

private:
    static RsFecTable* impl_;
};

class RsfecImpl
{
public:
    RsfecImpl();
    ~RsfecImpl();

    long fec_encode_packet(const fec_parms*     parms,
                           unsigned char* const src[],
                           const int            size[],
                           int                  index,
                           unsigned char*       fec);

    long fec_encode_length(const fec_parms*     parms,
                           unsigned char* const src[],
                           int                  size,
                           int                  index,
                           unsigned char*       fec_length);

    long decode_fec_packet(const fec_parms* parms,
                           unsigned char*   src[],
                           int              size[],
                           int              src_index[],
                           unsigned char*   dest[],
                           int              dest_index[],
                           int              dest_num);

    long             decode_fec_length(const fec_parms* parms,
                                       unsigned char*   src[],
                                       int              size,
                                       int              src_index[],
                                       unsigned char*   dest[],
                                       int              dest_index[],
                                       int              dest_num);
    const fec_parms* get_rs_params(int k, int n);

private:
    void rs_map_clear();

    std::unique_ptr<fec_parms> create_rsparms(int k, int n);
    int                        invert_vdm(std::vector<gf>* src, int k);
    void                       matmul(gf* a, gf* b, gf* c, int n, int k, int m);
    long build_decode_matrix(const fec_parms* code, gf* matrix, int index[]);
    int  invert_mat(gf* src, int k);

    CRSparmsMap                 rsparms_map_;
    std::shared_ptr<RsFecTable> rsfec_table_;

    typedef void (RsfecImpl::*addmul_func)(gf*       dst1,
                                           const gf* src1,
                                           gf        c,
                                           int       sz);
    void        addmul_normal(gf* dst1, const gf* src1, gf c, int sz);
    void        addmul_ssse3(gf* dst1, const gf* src1, gf c, int sz);
    void        addmul_avx2(gf* dst1, const gf* src1, gf c, int sz);
    void        addmul_neon(gf* dst1, const gf* src1, gf c, int sz);
    addmul_func addmul;
    void        init_addmul_func();
};
